
#include "cooperative-mutex.h"

#include <assert.h>
#include "fiber-context.h"
#include "fiber-env.h"

namespace dulymoo {

CooperativeMutex::CooperativeMutex(bool recursive) :
    recursive_(recursive),
    lock_lock_(),
    locked_(0),
    owner_(NULL),
    waiting_queue_()
{
}

CooperativeMutex::~CooperativeMutex()
{
}

bool CooperativeMutex::Lock()
{
    FiberEnv::ThreadContext *thread_context = FiberEnv::GetThreadContext();
    assert (thread_context != NULL);
    FiberContext *fiber_context = thread_context->GetCurrentFiber();
    assert (fiber_context != NULL);

    lock_lock_.Lock();
    if (locked_ == 0)
    {
        ++locked_;
        owner_ = fiber_context;
    }
    else
    {
        if (recursive_ && owner_ == fiber_context)
            ++locked_;
        else
        {
            fiber_context->SetState("mutex.lock");
            thread_context->Schedule(CompleteLock, reinterpret_cast<void*>(this));
            // lock_lock_ will be unlock'ed in CompleteLock, and when this fiber
            // is re-activated, it then hold the CooperativeMutex
            return true;
        }
    }
    lock_lock_.Unlock();
}

void CooperativeMutex::CompleteLock(FiberEnv::ThreadContext *thread_context, void* arg)
{
    CooperativeMutex *mutex = reinterpret_cast<CooperativeMutex*>(arg);
    assert (mutex != NULL);

    mutex->waiting_queue_.push_back(thread_context->GetCurrentFiber());
    mutex->lock_lock_.Unlock();
}

bool CooperativeMutex::TryLock()
{
    FiberEnv::ThreadContext *thread_context = FiberEnv::GetThreadContext();
    assert (thread_context != NULL);
    FiberContext *fiber_context = thread_context->GetCurrentFiber();
    assert (fiber_context != NULL);

    bool locked = false;

    lock_lock_.Lock();
    if (locked_ == 0)
    {
        ++locked_;
        owner_ = fiber_context;
        locked = true;
    }
    else if (recursive_ && owner_ == fiber_context)
    {
        ++locked_;
        locked = true;
    }
    lock_lock_.Unlock();

    return locked;
}

void CooperativeMutex::Unlock()
{
    FiberEnv::ThreadContext *thread_context = FiberEnv::GetThreadContext();
    assert (thread_context != NULL);
    FiberContext *fiber_context = thread_context->GetCurrentFiber();
    assert (fiber_context != NULL);

    lock_lock_.Lock();
    assert (locked_ > 0);
    assert (owner_ == fiber_context);

    if (--locked_ == 0)
    {
        if (waiting_queue_.empty())
            owner_ = NULL;
        else
        {
            FiberContext* waiting_fiber = waiting_queue_.front();
            waiting_queue_.pop_front();

            ++locked_;
            owner_ = waiting_fiber;

            thread_context->ReadyFiber(waiting_fiber);
        }
    }
    lock_lock_.Unlock();
}

}  // namespace dulymoo

